package com.ByteDance.service.impl;

import com.ByteDance.mapper.VideoMapper;
import com.ByteDance.model.po.FavoriteEntity;
import com.ByteDance.model.po.UserInfos;
import com.ByteDance.model.po.VideoEntity;
import com.ByteDance.service.IFavoriteService;
import com.ByteDance.service.IUserService;
import com.ByteDance.service.IVideoService;
import com.ByteDance.utils.AliOSSUtils;
import com.aliyun.oss.OSS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jdk.nashorn.internal.ir.ReturnNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;


import javax.imageio.ImageIO;
import javax.xml.bind.DatatypeConverter;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.List;


/**
 * <p>
 * 视频表 服务实现类
 * </p>
 *
 * @author 朱哥
 * @since 2024-02-25
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class VideoServiceImpl extends ServiceImpl<VideoMapper, VideoEntity> implements IVideoService {

    private final IUserService iUserService;
    private final AliOSSUtils aliOSSUtils;

    private static final String DEFAULT_IMG_FORMAT = "jpg";
    @Override
    public String uploadCover(String cover,String videoPath,String extension) throws IOException {
        //截取视频的一帧作为封面
        try {
            /**
             * 1.使用 FFmpegFrameGrabber 类加载视频文件，并获取视频的总帧数
             */
            FFmpegFrameGrabber ff = new FFmpegFrameGrabber(videoPath);
            //在使用FFmpegFrameGrabber类加载视频文件后，调用start()方法会启动视频帧抓取的过程。
            ff.start();
            int length = ff.getLengthInFrames();//frame 帧  --- 获取视频帧数
            /**
             * 2.循环逐帧读取视频内容，直到找到合适的帧作为封面。这段代码中提供了一个简单的逻辑，跳过前5帧以避免全黑图片。
             */
            int i = 0;//用于追踪当前处理的帧数，初始值为0。
            Frame f;//声明一个Frame对象f，用于存储每次从视频中抓取的帧数据，初始值为null。
            /**
             * 3.使用 Java2DFrameConverter 将视频帧转换为 BufferedImage 对象，方便后续处理和保存。
             */
            Java2DFrameConverter converter = new Java2DFrameConverter();
            BufferedImage bi = null;
            while (i < length) {
                f = ff.grabFrame();
                // 过滤前5帧，避免出现全黑的图片，依自己情况而定
                if ((i >= 5) && (f.image != null)) {//这段代码实际上是在跳过前5帧，并在后续帧中寻找第一个非全黑的帧
                    bi = converter.getBufferedImage(f);//将 BufferedImage 对象转换为字节数组
                    log.info("图片截取成功，图片信息：" + bi);
                    //判断当前所截帧是否全黑
                    boolean nonBlackFrame = isNonBlackFrame(bi);
                    if (nonBlackFrame){
                        break;
                    }
                }
                i++;
            }

            /**
             * 4.将 BufferedImage 对象转换为字节数组并通过 ImageIO.write 方法写入 ByteArrayOutputStream 中。
             */
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ImageIO.write(bi, DEFAULT_IMG_FORMAT , baos);//DEFAULT_IMG_FORMAT不带"."
            byte[] bytes = baos.toByteArray();
            String fileName = cover.split("/")[1];
            MultipartFile multipartFile = new MockMultipartFile(fileName,bytes);
            // 将封面保存到oss
            /**
             * 5.将封面图像通过 OSS 的 putObject 方法保存到指定的存储空间中
             */
            String url = aliOSSUtils.upload(cover, multipartFile);
            /**
             * 6.生成一个预签名的URL，设置有效期为7天，并将该URL作为结果返回。
             *    Date date = new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 7);
             *    String url = oss.generatePresignedUrl(bucketName, targetFile, date).toString();
             */
            /**
             * 7.最后关闭视频帧抓取器，记录日志并返回封面在 OSS 中的访问路径。
             */
            ff.stop();
            ff.close();
            log.info("视频封面保存成功：" + url);
            return url;
        } catch (Exception e) {
            throw new RuntimeException("转换视频图片异常");
        }
    }

    @Override
    public void saveVideoMsg(int userId, String videoPath, String coverPath, String title) {
        VideoEntity video = new VideoEntity();
        video.setAuthorId(userId);
        video.setPlayUrl(videoPath);
        video.setCoverUrl(coverPath);
        video.setTitle(title);
        video.setPublishTime(new Timestamp(System.currentTimeMillis()));
        baseMapper.insert(video);
        //作品数+1
        UserInfos userInfos = iUserService.getById(userId);
        iUserService.lambdaUpdate()
                .set(UserInfos::getWorkCount,userInfos.getWorkCount() + 1)
                .eq(UserInfos::getUserId,userId)
                .update();
    }

    @Override
    public List<VideoEntity> searchVideosByUserId(Integer userId) {
        List<VideoEntity> list = lambdaQuery().eq(VideoEntity::getAuthorId, userId).
                orderByDesc(VideoEntity::getPublishTime).list();
        return list;
    }



    /**
     * isNonBlackFrame()方法用于判断给定的BufferedImage对象是否不是全黑的帧。它会遍历图像的每个像素，
     * 并检查每个像素的RGB值是否大于设定的阈值。如果存在像素值大于阈值的像素，就认为当前帧不是全黑的帧。
     * @param image
     * @return
     */
    private boolean isNonBlackFrame(BufferedImage image) {
        int blackThreshold = 10; // 设置一个阈值，用于判断是否为全黑的阈值
        for (int x = 0; x < image.getWidth(); x++) {
            for (int y = 0; y < image.getHeight(); y++) {
                int rgb = image.getRGB(x, y);
                /**
                 * & 0xFF：接着使用位与操作符 & 和 0xFF（即 255 的十六进制表示），
                 * 可以将整数的高于8位的部分全部清零，这样就只保留了红色通道值所在的那8位数据。
                 * & 0xFF：接着使用位与操作符 & 和 0xFF（即 255 的十六进制表示），
                 * 可以将整数的高于8位的部分全部清零，这样就只保留了红色通道值所在的那8位数据
                 */
                int red = (rgb >> 16) & 0xFF;
                int green = (rgb >> 8) & 0xFF;
                int blue = rgb & 0xFF;
                if (red > blackThreshold || green > blackThreshold || blue > blackThreshold) {
                    return true; // 如果存在像素值大于阈值的像素，则认为不是全黑的帧
                }
            }
        }
        return false; // 如果所有像素值都小于阈值，则认为是全黑的帧
    }
}
